﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lexer;
using Lexer.SyntaxTree;
namespace RPGProject.GameWorld
{
    /// <summary>
    /// A class to run scripts.
    /// </summary>
    class ScriptRunner : GameObjects.IGOEventListener
    {
        /// <summary>
        /// A random number generator for all scripts.
        /// </summary>
        private static Random rand = new Random();
        /// <summary>
        /// The world the ScriptRunner belongs to.
        /// </summary>
        private World world;
        /// <summary>
        /// The script to be run.
        /// </summary>
        private readonly Script script;
        /// <summary>
        /// A stack for storing data in execution.
        /// </summary>
        private Dictionary<String, object> stack;
        private ScriptRunner(String filename, World world)
        {
            this.stack = new Dictionary<String, object>();
            this.script = new Script(filename);
            this.world = world;
        }
        /// <summary>
        /// Parses a script from file for a world.
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="world"></param>
        /// <returns></returns>
        public static ScriptRunner CreateScript(String filename, World world)
        {
            return new ScriptRunner(filename, world);
        }
        /// <summary>
        /// The Listen method checks for subscripts and such.  Runs them accordingly.  Throws errors if the script breaks.  Does not break game.
        /// </summary>
        /// <param name="eventParam"></param>
        /// <returns></returns>
        public GameObjects.GameObjectEvent Listen(GameObjects.GameObjectEvent eventParam)
        {
            try
            {
                // see if we need to deal with that event.
                if (this.script.HasSubscript(eventParam.Name))
                {
                    SubscriptSyntaxNode sne = this.script[eventParam.Name];
                    SyntaxNode next = sne.NextNode;
                    EventSyntaxNode esn;
                    IfSyntaxNode isn;
                    SetSyntaxNode ssn;
                    SyntaxNode sn;
                    IdentifierSyntaxNode identSN;
                    GameObjects.GameObjectEvent outgoingEvent;
                    do
                    {
                        // if were dealing with an event line
                        if (next is EventSyntaxNode)
                        {
                            esn = next as EventSyntaxNode;
                            outgoingEvent = new GameObjects.GameObjectEvent(esn.Name);
                            // send the event out with the relevant parameters
                            for (int i = 0; i < esn.Count; i++)
                            {
                                identSN = (IdentifierSyntaxNode)esn[i].LHS;
                                sn = esn[i].RHS;
                                outgoingEvent[identSN.Value] = this.DealWith(sn, eventParam);

                            }
                            this.world.Listen(outgoingEvent);
                            // advance the node
                            next = next.NextNode;
                        }
                        // if were dealing with a conditional
                        else if (next is IfSyntaxNode)
                        {
                            isn = next as IfSyntaxNode;
                            // grab the operands
                            Object arg1 = this.DealWith(isn.Condition.LHS, eventParam);
                            Object arg2 = this.DealWith(isn.Condition.RHS, eventParam);
                            // compare them using .Equals
                            switch (isn.Condition.OpCode)
                            {
                                case "==":
                                    if (arg1.Equals(arg2))
                                        next = isn.IfTrue;
                                    else
                                        next = isn.IfFalse;
                                    break;
                                case "!=":
                                    if (!arg1.Equals(arg2))
                                        next = isn.IfTrue;
                                    else
                                        next = isn.IfFalse;
                                    break;
                                default:
                                    throw new Exception("Unknown operator encountered.");
                            }
                        }
                        // if were dealing with a set line
                        else if (next is SetSyntaxNode)
                        {
                            ssn = next as SetSyntaxNode;
                            object arg1;
                            object arg2;
                            int int1;
                            int int2;
                            object setter;
                            // deal with the operators
                            switch(ssn.Expr.OpCode)
                            {
                                case "=":
                                    // initialization section
                                    switch (((IdentifierSyntaxNode)ssn.Expr.LHS).Value)
                                    {
                                        case "int":
                                            setter = ((NumericLiteralSyntaxNode)ssn.Expr.RHS).Value;
                                            break;
                                        case "string":
                                            setter = ((StringLiteralSyntaxNode)ssn.Expr.RHS).Value;
                                            break;
                                        default:
                                            setter = null;
                                            throw new Exception("Unknown set type encountered.");
                                    }
                                    break;
                                case "+":
                                    int1 = (int)this.DealWith(ssn.Expr.LHS, eventParam);
                                    int2 = (int)this.DealWith(ssn.Expr.RHS, eventParam);
                                    setter = int1 + int2;
                                    break;
                                case "$":
                                    int1 = (int)this.DealWith(ssn.Expr.LHS, eventParam);
                                    int2 = (int)this.DealWith(ssn.Expr.RHS, eventParam);
                                    setter = ScriptRunner.rand.Next(int1, int2 + 1);
                                    break;
                                case "-":
                                    int1 = (int)this.DealWith(ssn.Expr.LHS, eventParam);
                                    int2 = (int)this.DealWith(ssn.Expr.RHS, eventParam);
                                    setter = int1 - int2;
                                    break;
                                case "/":
                                    int1 = (int)this.DealWith(ssn.Expr.LHS, eventParam);
                                    int2 = (int)this.DealWith(ssn.Expr.RHS, eventParam);
                                    setter = int1 / int2;
                                    break;
                                case "*":
                                    int1 = (int)this.DealWith(ssn.Expr.LHS, eventParam);
                                    int2 = (int)this.DealWith(ssn.Expr.RHS, eventParam);
                                    setter = int1 * int2;
                                    break;
                                case "%":
                                    int1 = (int)this.DealWith(ssn.Expr.LHS, eventParam);
                                    int2 = (int)this.DealWith(ssn.Expr.RHS, eventParam);
                                    setter = int1 % int2;
                                    break;
                                case "&":
                                    arg1 = this.DealWith(ssn.Expr.LHS, eventParam).ToString();
                                    arg2 = this.DealWith(ssn.Expr.RHS, eventParam).ToString();
                                    setter = (String)arg1 + (String)arg2;
                                    break;
                                default:
                                    setter = null;
                                    throw new Exception("Unknown operator encountered.");

                            }
                            this.stack[ssn.VariableName] = setter;
                            // advance the node
                            next = next.NextNode;
                        }
                        else
                        {
                            throw new Exception("Unknown Node Type encountered.");
                        }
                    } while (next != null);
                }
                return null;
            }
            catch (Exception e)
            {
                // let the user know a script borked.
                GameObjects.GameObjectEvent ge = new GameObjects.GameObjectEvent("exception");
                ge["message"] = e.Message;
                this.world.Listen(ge);
                return null;
            }
        }

        private object DealWith(SyntaxNode sn, GameObjects.GameObjectEvent eventParam)
        {
            // deal with an operand
            object ret = null;
            if (sn is IdentifierSyntaxNode)
            {
                // check the stack
                ret = this.stack[((IdentifierSyntaxNode)sn).Value];
            }
            else if (sn is NumericLiteralSyntaxNode)
            {
                // grab the int value
                ret = ((NumericLiteralSyntaxNode)sn).Value;
            }
            else if (sn is StringLiteralSyntaxNode)
            {
                // grab the string value
                ret = ((StringLiteralSyntaxNode)sn).Value;
            }
            else if (sn is PropertySyntaxNode)
            {
                // get the property
                PropertySyntaxNode psn = (PropertySyntaxNode)sn;
                PropertyLiteral propLit = PropertyLiteral.ParseProperty(psn.PropertyName);
                // check to see if the property has 1 element in its reference
                if (propLit.Size == 1)
                {
                    if (eventParam.Has(propLit.Front))
                    {
                        ret = eventParam[propLit.Front];
                    }
                    else
                    {
                        ret = this.world[propLit];
                    }
                }
                else
                {
                    if (eventParam.Has(propLit.Front))
                    {
                        GameObjects.IPropertyRetrievable ipr = (GameObjects.IPropertyRetrievable)(eventParam[psn[0]]);
                        ret = ipr[propLit.Next];
                    }
                    else
                    {
                        ret = this.world[propLit.Next];
                    }
                }
                // first check the event for the property
                // then ask the world to see if it can get it

            }
            return ret;
        }
    }
}
